module MEMCPY_Controller(
	//Host side
	iCLK,									//50 MHz
	iRST_N,								//Reset
	iStartCopying,						//Start copying
	oBusy,								//MEMCPY busy
	iMode,								//0: SRAM -> CIRCLE_BUFF ; 1: const -> SRAM
	iRadius,
	iOrientation,
	oRadiusFloat,
	oMemLength,
	//CAM side
	iY,
	//Memory side CIRCLE_BUFFER (writes only)
	oCIRCLE_BUFF_ADDR,
	oCIRCLE_BUFF_DATA,
	oCIRCLE_BUFF_WREN,
	//Memory side SRAM (reads or writes)
	oSRAM_ADDR,
	iSRAM_DATA,
	oSRAM_DATA,
	oSRAM_CLK,
	oSRAM_WE_N,
	//Acos side
	oTrigStart,
	oTrigFunc,
	iTrigBusy,
	iTrigError,
	oTrigData,
	iTrigResult
);

localparam PI2			= 32'h40c90fdb;	// 2*Pi

input iCLK,iStartCopying,iMode, iRST_N;
output oBusy;
assign oBusy = copying;
input[7:0] iRadius;
input[31:0] iOrientation;
input[9:0] iY;
output reg [31:0] oRadiusFloat;
output reg [10:0] oMemLength;
//CIRCLE_BUFFER
output reg [10:0] oCIRCLE_BUFF_ADDR;
output reg [7:0] oCIRCLE_BUFF_DATA;
output reg oCIRCLE_BUFF_WREN;
//SRAM
output reg [17:0] oSRAM_ADDR;
input [15:0] iSRAM_DATA;
output reg [15:0] oSRAM_DATA;
output reg oSRAM_CLK;
output reg oSRAM_WE_N;
//External acos
output reg oTrigStart;
output reg [1:0] oTrigFunc = 2'b10;
input iTrigBusy, iTrigError;
output reg [31:0] oTrigData;
input [31:0] iTrigResult;

reg prev_iStartCopying, copying, mode;
reg [4:0] state_memcpy;
reg[8:0] coordX, coordY;
reg[7:0] radius;
reg [5:0] timer;
reg [15:0] indata;
reg [31:0] tmp, orient;
reg[11:0] alpha;

always@(posedge iCLK or negedge iRST_N)
begin
	if(!iRST_N) begin
		prev_iStartCopying <= 1'b1;
		copying <= 1'b0;
		oCIRCLE_BUFF_ADDR <= 0;
		oCIRCLE_BUFF_WREN <= 1'b0;
		oCIRCLE_BUFF_DATA <= 0;
		oSRAM_CLK <= 1'b1;
		oSRAM_WE_N <= 1'b1;
		oSRAM_DATA <= 0;
		oTrigStart <= 1'b0;
	end
	else begin
		prev_iStartCopying <= iStartCopying;	
		if(({prev_iStartCopying, iStartCopying} == 2'b01) && !copying) begin
			copying <= 1'b1;
			state_memcpy <= 5'd15;
			mode <= iMode;
			coordX <= 0;
			coordY <= 0;
			radius <= iRadius;
			orient <= iOrientation;
			oTrigStart <= 1'b0;
			IntToFP1A <= iRadius;
			timer <= 6'b0;
		end		
		else if(copying) begin	
			case(state_memcpy)
				5'd15: begin
					timer <= timer + 1'b1;					
					if(timer == 6'd6) begin
						timer <= 6'd0;
						state_memcpy <= 5'd16;
						MultA <= IntToFP1Res;
						MultB <= PI2;
						oRadiusFloat <= IntToFP1Res;
					end
				end
				5'd16: begin
					timer <= timer + 1'b1;					
					if(timer == 6'd6) begin
						timer <= 6'd0;
						state_memcpy <= 5'd17;
						MultA <= orient;
						MultB <= oRadiusFloat;
						FPToIntA <= MultRes;
					end
				end
				5'd17: begin
					timer <= timer + 1'b1;					
					if(timer == 6'd6) begin
						timer <= 6'd0;
						state_memcpy <= 5'd18;
						oMemLength <= FPToIntRes[10:0];
						FPToIntA <= MultRes;
					end
				end
				5'd18: begin
					timer <= timer + 1'b1;					
					if(timer == 6'd6) begin
						timer <= 6'd0;
						state_memcpy <= 5'd0;
						alpha <= FPToIntRes[11:0];
					end
				end	
				5'd0: begin
					oSRAM_WE_N <= 1'b1;
					oCIRCLE_BUFF_WREN <= 1'b0;						
					if(coordX == 480) begin
						coordX <= 0;
						coordY <= coordY + 1'b1;
						if(coordY == 480)copying <= 1'b0;
					end
					else coordX <= coordX + 1'b1;
					state_memcpy <= 4'd1;
				end
				5'd1: begin
					if((((240-coordX)*(240-coordX) + (240-coordY)*(240-coordY)) > ((radius-1) * (radius-1))) &&
						(((240-coordX)*(240-coordX) + (240-coordY)*(240-coordY)) < ((radius+2) * (radius+2))))
							state_memcpy <= mode ? 4'd2 : 4'd3;
					else state_memcpy <= 4'd0;
				end				
				5'd2: begin
					oSRAM_ADDR <= coordY * 480 + coordX;
					oSRAM_DATA <= 0;
					oSRAM_WE_N <= 1'b0;
					state_memcpy <= 4'd0;
				end
				5'd3: begin
					IntToFP1A <= (240 - coordY); 
					IntToFP2A <= (240-coordX)*(240-coordX) + (240-coordY)*(240-coordY);
					timer <= 6'b0;
					state_memcpy <=  4'd4;
				end
				5'd4: begin
					timer <= timer + 1'b1;					
					if(timer == 6'd6) begin
						timer <= 6'd0;
						state_memcpy <= 4'd5;
						SinglePrecFloatSqrtA <= IntToFP2Res;
					end
				end
				5'd5: begin
					timer <= timer + 1'b1;	
					if(timer == 6'd16) begin
						timer <= 6'd0;
						state_memcpy <=  4'd6;
						DivA <= IntToFP1Res;
						DivB <= SinglePrecFloatSqrtRes;
					end
				end
				5'd6: begin
					timer <= timer + 1'b1;					
					if(timer == 6'd6) begin
						timer <= 6'd0;
						state_memcpy <=  4'd7;
						oTrigData <= DivRes;						
						oTrigStart <= 1'b1;
					end
				end				
				5'd7: 
					if(iTrigBusy == 1'b1) state_memcpy <=  4'd8;
					else if(iTrigError == 1'b1)begin
						state_memcpy <=  4'd0;
						copying <= 1'b0;
					end
				5'd8: begin
					oTrigStart <= 1'b0;
					if(iTrigBusy == 1'b0) begin
						if(coordX < 241) begin
							tmp <= iTrigResult;
							state_memcpy <=  4'd10;
						end
						else begin
							Add1A <= PI2;
							Add1B <= {~iTrigResult[31],iTrigResult[30:0]};
							state_memcpy <=  4'd9;
						end
					end
				end
				5'd9: begin
					timer <= timer + 1'b1;					
					if(timer == 6'd7) begin
						timer <= 6'd0;
						state_memcpy <= 4'd10;
						tmp <= Add1Res;
					end
				end				
				5'd10: begin
						state_memcpy <= 4'd11;
						MultA <= tmp;
						MultB <= oRadiusFloat;
				end
				5'd11: begin
					timer <= timer + 1'b1;					
					if(timer == 6'd5) begin
						timer <= 6'd0;
						state_memcpy <=  4'd12;
						FPToIntA <= MultRes;
					end
				end
				5'd12: begin
					timer <= timer + 1'b1;					
					if(timer == 6'd6) begin
						timer <= 6'd0;
						oSRAM_ADDR <= coordY * 480 + coordX;
						oSRAM_CLK <= 1'b0;
						state_memcpy <=  4'd13;
						tmp <= FPToIntRes + alpha;
					end
				end
				5'd13: begin
					indata <= iSRAM_DATA;
					oSRAM_CLK <= 1'b1;
					state_memcpy <= 4'd14;
					if(tmp > oMemLength) tmp <= tmp - oMemLength;
					else if(tmp < 0) tmp <= tmp + oMemLength;
				end
				5'd14: begin
					oCIRCLE_BUFF_DATA <= indata[7:0];
					oCIRCLE_BUFF_ADDR <= tmp[10:0];
					oCIRCLE_BUFF_WREN <= 1'b1;
					oSRAM_ADDR[17:11] <= 7'd0;
					oSRAM_ADDR[10:0] <= tmp[10:0];
					oSRAM_WE_N <= 1'b0;
					oSRAM_DATA <= ~indata;
					state_memcpy <= 4'd0;
				end
			endcase
		end
	end
end

//6T
reg [31:0] IntToFP1A;
wire [31:0] IntToFP1Res;
IntToSinglePrecFloatConvert u0(
	.clock(iCLK),
	.dataa(IntToFP1A),
	.result(IntToFP1Res)
);

//6T
reg [31:0] IntToFP2A;
wire [31:0] IntToFP2Res;
IntToSinglePrecFloatConvert u1(
	.clock(iCLK),
	.dataa(IntToFP2A),
	.result(IntToFP2Res)
);

//6T
reg [31:0] FPToIntA;
wire [31:0] FPToIntRes;
SinglePrecFloatToInt u2(
	.clock(iCLK),
	.dataa(FPToIntA),
	.result(FPToIntRes)
);

//6T
reg[31:0] DivA, DivB;
wire[31:0] DivRes;
SinglePrecFloatDiv u3(
	.dataa(DivA),
	.datab(DivB),
	.result(DivRes),
	.clock(iCLK)
);

//5T
reg[31:0] MultA, MultB;
wire[31:0] MultRes;
SinglePrecFloatMult u4(
	.dataa(MultA),
	.datab(MultB),
	.result(MultRes),
	.clock(iCLK)
);

//7T
reg[31:0] Add1A, Add1B;
wire[31:0] Add1Res;
SinglePrecFloatAdd u5(
	.clock(iCLK),
	.dataa(Add1A),
	.datab(Add1B),
	.result(Add1Res)
);


//16T
reg [31:0] SinglePrecFloatSqrtA;
wire [31:0] SinglePrecFloatSqrtRes;
SinglePrecFloatSqrt u6(
	.clock(iCLK),
	.data(SinglePrecFloatSqrtA),
	.result(SinglePrecFloatSqrtRes)
);

endmodule 